package wechat

import (
	"anban/utils"
	"crypto/tls"
	"encoding/xml"
	"fmt"
	"github.com/astaxie/beego"
	"net/http"
	"sort"
	"strings"
)

type PapPayApply struct {
	XMLName        xml.Name `xml:"xml"`              // XML根节点名字
	Appid          string   `xml:"appid"`            // appid是商户在微信申请公众号或移动应用成功后分配的帐号ID
	MchId          string   `xml:"mch_id"`           // 商户号是商户在微信申请微信支付成功后分配的帐号ID
	SubMchId       string   `xml:"sub_mch_id"`       // 微信支付分配的子商户号
	NonceStr       string   `xml:"nonce_str"`        // 随机字符串，不长于32位
	Sign           string   `xml:"sign"`             // 签名
	Body           string   `xml:"body"`             // 商品或支付单简要描述
	OutTradeNo     string   `xml:"out_trade_no"`     // 商户系统内部的订单号,32个字符内、可包含字母
	TotalFee       int64    `xml:"total_fee"`        // 订单总金额，单位为分，只能为整数
	SpbillCreateIp string   `xml:"spbill_create_ip"` // 调用微信支付API的机器IP
	NotifyUrl      string   `xml:"notify_url"`       // 接受扣款结果异步回调通知的ur
	TradeType      string   `xml:"trade_type"`       // 交易类型PAP-微信委托代扣支付
	ContractId     string   `xml:"contract_id"`      // 签约成功后，微信返回的委托代扣协议id
}

type PapPayApplyRes struct {
	XMLName        xml.Name `xml:"xml"`              // XML根节点名字
	ReturnCode     string   `xml:"return_code"`      // SUCCESS/FAIL 此字段是通信标识，非交易标识，交易是否成功需要查看result_code来判断
	ReturnMsg      string   `xml:"return_msg"`       // 返回信息，如非空，为错误原因
	Appid          string   `xml:"appid"`            // 微信支付分配的公众账号id
	SubAppid       string   `xml:"sub_appid"`        // 微信分配的子商户公众账号ID
	MchId          string   `xml:"mch_id"`           // 微信支付分配的商户号
	SubMchId       string   `xml:"sub_mch_id"`       // 微信支付分配的子商户号
	DeviceInfo     string   `xml:"device_info"`      // 微信支付分配的终端设备号
	NonceStr       string   `xml:"nonce_str"`        // 随机字符串，不长于32位
	Sign           string   `xml:"sign"`             // 签名
	ResultCode     string   `xml:"result_code"`      // 业务结果 SUCCESS/FAIL
	ErrCode        string   `xml:"err_code"`         // 错误码
	ErrCodeDes     string   `xml:"err_code_des"`     // 错误码描述
	Openid         string   `xml:"openid"`           // 用户在商户appid下的唯一标识
	SubOpenid      string   `xml:"sub_openid"`       // sub_appid下，用户的唯一标识
	IsSubscribe    string   `xml:"is_subscribe"`     // 用户是否关注公众账号，Y-关注，N-未关注
	SubIsSubscribe string   `xml:"sub_is_subscribe"` // 用户是否关注子公众账号，Y-关注，N-未关注
	BankType       string   `xml:"bank_type"`        // 银行类型
	TotalFee       string   `xml:"total_fee"`        // 订单总金额，单位为分(trade_state为SUCCESS和REFUND时才有返回)
	FeeType        string   `xml:"fee_type"`         // 货币类型
	CashFee        string   `xml:"cash_fee"`         // 现金支付金额订单现金支付金额
	CashFeeType    string   `xml:"cash_fee_type"`    // 货币类型
	TradeState     string   `xml:"trade_state"`      // SUCCESS—支付成功 REFUND—转入退款 NOTPAY—未支付 CLOSED—已关闭 ACCEPT—已接收，等待扣款 PAY_FAIL--支付失败(其他原因，如银行返回失败)
	CouponFee      string   `xml:"coupon_fee"`       // 代金券或立减优惠金额<=订单总金额，订单总金额-代金券或立减优惠金额=现金支付金额
	CouponCount    string   `xml:"coupon_count"`     // 代金券或立减优惠使用数量
	CouponId0      string   `xml:"coupon_id_0"`      // 代金券或立减优惠ID,$n为下标，从0开始编号
	CouponFee0     string   `xml:"coupon_fee_0"`     // 单个代金券或立减优惠支付金额,$n为下标，从0开始编号
	TransactionId  string   `xml:"transaction_id"`   // 微信支付订单号
	OutTradeNo     string   `xml:"out_trade_no"`     // 商户系统的订单号，与请求一致
	Attach         string   `xml:"attach"`           // 商家数据包，原样返回
	TimeEnd        string   `xml:"time_end"`         // 支付完成时间，格式为yyyyMMddHHmmss，如2009年12月25日9点10分10秒表示为20091225091010
	ContractId     string   `xml:"contract_id"`      // 签约成功后，微信返回的委托代扣协议id
}

type PapPayApplyRet struct {
	XMLName    xml.Name `xml:"xml"`
	ReturnCode string   `xml:"return_code"` // SUCCESS/FAIL SUCCESS表示商户接收通知成功并校验成功
	ReturnMsg  string   `xml:"return_msg"`  // 返回信息，如非空，为错误原因
}

func NewPapPayApply(totalFee int64, outTradeNo, contractId string) *PapPayApply {
	appid := beego.AppConfig.String("wechatappid")
	mchid := beego.AppConfig.String("mchid")
	submchid := beego.AppConfig.String("submchid")
	nonceStr := utils.GetRandomString(32)
	apiKey := beego.AppConfig.String("apikey")
	notifyUrl := beego.AppConfig.String("pappaynotifyurl")
	papPayApply := &PapPayApply{
		Appid:          appid,
		MchId:          mchid,
		SubMchId:       submchid,
		NonceStr:       nonceStr,
		Sign:           "",
		Body:           "安伴校园服务扣费",
		OutTradeNo:     outTradeNo,
		TotalFee:       totalFee,
		SpbillCreateIp: "127.0.0.1",
		NotifyUrl:      notifyUrl,
		TradeType:      "PAP",
		ContractId:     contractId,
	}
	papPayApplyMap := map[string]interface{}{
		"appid":            papPayApply.Appid,
		"mch_id":           papPayApply.MchId,
		"sub_mch_id":       papPayApply.SubMchId,
		"nonce_str":        papPayApply.NonceStr,
		"body":             papPayApply.Body,
		"out_trade_no":     papPayApply.OutTradeNo,
		"total_fee":        papPayApply.TotalFee,
		"spbill_create_ip": papPayApply.SpbillCreateIp,
		"notify_url":       papPayApply.NotifyUrl,
		"trade_type":       papPayApply.TradeType,
		"contract_id":      papPayApply.ContractId,
	}
	keySlice := []string{}
	for k, _ := range papPayApplyMap {
		keySlice = append(keySlice, k)
	}
	sort.Strings(keySlice)
	paramStr := ""
	for _, v := range keySlice {
		if v == "total_fee" {
			paramStr += fmt.Sprintf("%s=%d&", v, papPayApplyMap[v])
			continue
		}
		paramStr += fmt.Sprintf("%s=%s&", v, papPayApplyMap[v])
	}
	paramStr += fmt.Sprintf("key=%s", apiKey)
	sign := strings.ToUpper(utils.Encode(paramStr))
	papPayApply.Sign = sign
	return papPayApply
}

// 申请扣款
func (p *PapPayApply) Apply() error {
	contentType := "application/xml;charset=utf-8"
	url := "https://api.mch.weixin.qq.com/pay/partner/pappayapply"
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}
	content, err := xml.Marshal(p)
	if err != nil {
		return utils.NewError(0, "申请扣款结构体无法转化为XML")
	}
	_, err = client.Post(url, contentType, strings.NewReader(string(content)))
	if err != nil {
		return utils.NewError(0, "申请扣款请求失败")
	}
	return nil
}

// 申请扣款异步通知签名校验
func (p *PapPayApplyRes) CheckSign() bool {
	apiKey := beego.AppConfig.String("apikey")
	papPayApplyResMap := map[string]string{
		"return_code":      p.ReturnCode,
		"return_msg":       p.ReturnMsg,
		"appid":            p.Appid,
		"sub_appid":        p.SubAppid,
		"mch_id":           p.MchId,
		"sub_mch_id":       p.SubMchId,
		"device_info":      p.DeviceInfo,
		"nonce_str":        p.NonceStr,
		"result_code":      p.ResultCode,
		"err_code":         p.ErrCode,
		"err_code_des":     p.ErrCodeDes,
		"openid":           p.Openid,
		"sub_openid":       p.SubOpenid,
		"is_subscribe":     p.IsSubscribe,
		"sub_is_subscribe": p.SubIsSubscribe,
		"bank_type":        p.BankType,
		"total_fee":        p.TotalFee,
		"fee_type":         p.FeeType,
		"cash_fee":         p.CashFee,
		"cash_fee_type":    p.CashFeeType,
		"trade_state":      p.TradeState,
		"coupon_fee":       p.CouponFee,
		"coupon_count":     p.CouponCount,
		"coupon_id_0":      p.CouponId0,
		"coupon_fee_0":     p.CouponFee0,
		"transaction_id":   p.TransactionId,
		"out_trade_no":     p.OutTradeNo,
		"attach":           p.Attach,
		"time_end":         p.TimeEnd,
		"contract_id":      p.ContractId,
	}
	keySlice := []string{}
	for k, _ := range papPayApplyResMap {
		keySlice = append(keySlice, k)
	}
	sort.Strings(keySlice)
	paramStr := ""
	for _, v := range keySlice {
		if len(papPayApplyResMap[v]) > 0 {
			paramStr += fmt.Sprintf("%s=%s&", v, papPayApplyResMap[v])
		}
	}
	signStr := fmt.Sprintf("%skey=%s", paramStr, apiKey)
	sign := strings.ToUpper(utils.Encode(signStr))
	if sign != p.Sign {
		return false
	}
	return true
}

func GetPapPayApplyRet(code, msg string) string {
	ret := PapPayApplyRet{
		ReturnCode: code,
		ReturnMsg:  msg,
	}
	content, _ := xml.Marshal(ret)
	return string(content)
}
